from driver import UART
# from driver import ADC
import utime as time
from driver import TIMER
import _thread
import serial_base
import ubinascii
import ustruct
import urandom
# import app_config
import channel_dry
# import sleep_mode


class HmiDrver(object):
    def __init__(self,port="rs232",buffer_size=50):
        self.uart_hmi = UART()
        self.uart_hmi.open(port)
        self.uart_hmi.on(self.recive_from_hmi)
        # 空白和非0通道时间间隔
        self.interval_1_9 =30
        self.interval_0 =30
        # 设置定时器
        self.timer0 = TIMER(0)
        self.timer0.open(mode=self.timer0.PERIODIC,period=self.interval_0*1000 , callback=self.timer0_cb)
        self.timer0.stop()
    
        # 轮询与阶段控制
        self.sequence_channels = []           # 选择的通道集合（1..9）
        self.sequence_index = 0               # 非0阶段轮询索引
        self.current_channel = 0              # 当前打开的通道（0为空白）
        self.phase = 'blank'                  # 当前阶段：'blank' 或 'nonzero'
        self._ctrlid_to_channel_map = {       # 控件ID -> 通道编号
            '0d':1,'1a':2,'1d':3,'1e':4,'20':5,'23':6,'21':7,'22':8,'24':9
        }
        self._channel_to_anim = {             # 通道编号 -> 动画控件ID
            1:45,2:42,3:39,4:46,5:43,6:40,7:47,8:44,9:41,0:51
        }   
        #标志位
        self.collect_flag = False     # 切换自动采集和软件连接的标志位
        self.recieve_data = None
        #回调函数
        self.hmi_hander = None  # 读取传感器 回调
        self.RTCcalback = None  # RTC 回调
        # TF卡变量
        self.tf_file_path = '/sdcard0/'
        self.TfObj  = None
    
        self.interval_logger = 3  # 采集时间间隔
       
        # 日期时间变量
        self.time_list = {'year':2024,'month':12,'day':1,'hour':0,
                       'minute':0,'second':0,'week':1}  # 时间列表      
        # 数据帧变量
        self.frames =[]            # 接收数据帧
        self.cmd_head = ''         # 帧头
        self.cmd_type = ''         # 命令类型(UPDATE_CONTROL)
        self.ctrl_msg = ''         # CtrlMsgType-指示消息的类型
        self.screen_id = ''        # 产生消息的画面ID
        self.control_id = ''       # 产生消息的控件ID
        self.control_type = ''     # 控件类型
        self.param = ''            # 可变长度参数
        self.cmd_tail = ''         # 帧尾

        self.display_run()      # 开启UI更新线程

    def get_current_channel(self):
        return self.current_channel

    def rtc_set(self,datatime):
        second = str(datatime[5])
        minute = str(datatime[4])
        hour = str(datatime[3])
        day = str(datatime[2])
        weekend = str(datatime[6])
        morth = str(datatime[1])
        year = str(datatime[0]-2000)
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8(0x81)
        cmd+= serial_base.TX_8(second)
        cmd+=serial_base.TX_8(minute)
        cmd+=serial_base.TX_8(hour)
        cmd+=serial_base.TX_8(day)
        cmd+=serial_base.TX_8(weekend)
        cmd+=serial_base.TX_8(morth)
        cmd += serial_base.TX_8(year)
        cmd += serial_base.END_CMD()
        self.uart_hmi.write(cmd)
             
    def read_recive_data(self,data):
        """
        brief: 获取串口屏数据帧
        return: 返回最后一帧数据
        """
        data_head = "ee"
        data_end = "fffcffff"
        hand_index = 0
        size = len(data)
        # 二进制字节串转16进制再解码为字符串
        datastr = ubinascii.hexlify(data).decode('utf-8')
        # print(datastr)
        # 寻找所有的帧数据
        while size > 0:
            # 寻找帧头
            hand_index = datastr.find(data_head,hand_index)
            if hand_index == -1:
                break
            # 寻找帧尾
            end_index = datastr.find(data_end,hand_index)
            if end_index == -1:
                break
            end_index += len(data_end)
            frame  = datastr[hand_index:end_index]
            self.frames.append(frame)
            hand_index = end_index 
            # print(hand_index)
        if self.frames:
            # 获取最后一个帧
            last_frame = self.frames[-1]
            self.frames= []                 # 清空当前阵列表以准备下一次读取
            # print(last_frame)
            return last_frame
        else:
            return None

    def display_run(self):
        _thread.start_new_thread(self.display_thread,())   
    def display_thread(self):        
        while True:
            try:
                time.sleep(2)
                self.UpdataUI()
            except Exception as e:
                print("display thread error:",e)
           
    def recive_from_hmi(self,recdata):  
        
        data = self.read_recive_data(recdata)    # 获取串口屏数据
        if data:
            # print('HMI recive data:',data)
            self.pare_cmd_from_buffer(data)
            self.process_message()  

    def pare_cmd_from_buffer(self,dataframe):
        """
        brief: 解析数据帧
        """
        self.cmd_head = dataframe[:2]              # 帧头   半个字节
        self.cmd_type = dataframe[2:4]             # 命令类型(UPDATE_CONTROL)如，0xB1
        self.ctrl_msg = dataframe[4:6]             # 指示消息的类型,如，0x11发送mcu或者0x10接收mcu信息
        self.screen_id = dataframe[6:10]           # 产生消息的画面ID
        self.control_id = dataframe[10:14]           # 产生消息的控件ID  1bytes
        self.control_type = dataframe[14:16]         # 控件类型 如，0x10为按钮
        self.param = dataframe[16:-8]                # 可变长度参数
        self.cmd_tail = dataframe[-8:]               # 帧尾

      # 信息处理流程  
    def process_message(self):
        control_type = {
            '10' : self.NotifyButton,  # 按钮
            '11' : self.NotifyText,    # 文本
            '1b' : self.NotifySelector,  # 选择控件
             
        }
        # print(self.cmd_type)
        if self.cmd_type == "b1":
            handler = control_type.get(self.control_type)
            if handler:
                handler(self.screen_id,self.control_id,self.param)
            else:
                print("Unknown control type")
        elif self.cmd_type == 'f7':
              self.set_rtc_from_hmi()
              
        else:
            return

                         
    def set_rtc_from_hmi(self):
        '''
        brief: 从显示屏设置的时间中获取RTC时间
        '''
        get_rtc = (self.ctrl_msg + self.screen_id + self.control_id 
        +self.control_type +self.param )
        year = int(get_rtc[0:2])  
        month = int(get_rtc[2:4])  
        week = int(get_rtc[4:6])   
        day = int(get_rtc[6:8])     
        hour = int(get_rtc[8:10])   
        minute = int(get_rtc[10:12]) 
        second = int(get_rtc[12:14])
        # 设置设备时间
        if self.RTCcalback:
            self.RTCcalback(year=(2000+year),month=month,day=day,hour=hour,minute=minute,second=second)

 
    def NotifyButton(self,screen_id, control_id,state):
        '''
          brief  按钮控件通知  
          details  当按钮状态改变，执行此函数
          param screen_id 画面ID
          param control_id 控件ID
          param state 按钮状态:0 弹起,1按下
        '''
        screen_id = screen_id[-2:]
        control_id = control_id[-2:]
        state = state[-2:]
        # print(screen_id,control_id)
        if screen_id == '00' and control_id == '0e':
            self.ReadRTC() 
 
        if screen_id == '00' and control_id == '1c': 
            if state == '01':
                print("start logging")
                self.phase = 'nonzero'         
                self.sequence_index = 0
                # 清理当前通道并设置空白状态动画
                if self.current_channel:
                    self._turn_off_channel(self.current_channel)
                    self.current_channel = 0
                if not self.sequence_channels :
                    return
                self.collect_flag = True    #  同时数据采集开始
                self.AnimationPlayFrame(0, 53, 1)   # 采集提示灯
                self._turn_on_channel(self.current_channel)    # 空白先开始
                self.timer0.period(int(self.interval_0)*1000)
                self.timer0.start()
                
            elif state == '00':
                print("stop logging")
                self.collect_flag = False
                self.phase = 'blank'
                self.timer0.stop()
                # 停止时关闭所有通道与动画
                if self.current_channel:
                    self._turn_off_channel(self.current_channel)   # 非0通道关闭
                    self.current_channel = 0
                    self.AnimationPlayFrame(0, 52, 0)   # 1-9提示灯
                self.AnimationPlayFrame(0, 53, 0)   #  采集提示灯
                self._turn_off_channel(self.current_channel)  # 0通道关闭
            
        # 1-9 通道选择
        if screen_id == '00' and control_id in self._ctrlid_to_channel_map:
            ch = self._ctrlid_to_channel_map[control_id]
            if state == '01':
                if ch not in self.sequence_channels:
                    self.sequence_channels.append(ch)
                    self.sequence_channels.sort()  # 若需按按下顺序轮询，删除此行
                
            else:
                if ch in self.sequence_channels:
                    self.sequence_channels.remove(ch)
                # 若当前开启通道被取消，立即关闭
                if self.current_channel == ch:
                    self._turn_off_channel(ch)
                    self.current_channel = 0
            
        
    def NotifyText(self,screen_id, control_id,text):
        '''
          brief  文本控件通知  
          details  当文本通过屏幕按键更新，执行此函数
          param screen_id 画面ID
          param control_id 控件ID
          param  text     文本内容
        '''
        screen_id = screen_id[-2:]
        control_id = control_id[-2:]
        if screen_id == '00' and control_id == '19':
            try:
                ascii_string = ubinascii.unhexlify(text).decode('utf-8').split('\0')   # 将16进制转字节串再转字符串
                self.interval_0 = ascii_string[0]
                print("0:",self.interval_0)
            except Exception as e:
                print("Failed to update interval:",e)

        if screen_id == '00' and control_id == '1b':
            ascii_string = ubinascii.unhexlify(text).decode('utf-8').split('\0')
            self.interval_1_9 = ascii_string[0]
            print("1-9:",self.interval_1_9) 
        if screen_id == '00' and control_id == '32':
            ascii_string = ubinascii.unhexlify(text).decode('utf-8').split('\0')
            self.interval_logger = ascii_string[0]
            print("logger:",self.interval_logger)
        
    def NotifySelector(self,screen_id,control_id, item):
        ''' 
        *  \brief  滑动选择控件通知
        *  \param screen_id 画面ID
        *  \param control_id 控件ID
        *  \param item 当前列表索引值
        '''
        screen_id = screen_id[-2:]
        control_id = control_id[-2:]
        if(screen_id == '00' and control_id == '0c'): 
            pass
    
    def SetTextFloat(self,screen_id, control_id, floatvalue, precision):
        '''
          brief   设置文本单精度浮点值 
          param  screen_id 画面ID
          param  control_id 控件ID
          param  floatvalue 浮点数值
          param  precision 小数位数，若不够则末尾补零
        '''
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8(0xB1)
        cmd += serial_base.TX_8(0x07)
        cmd += serial_base.TX_16(screen_id)
        cmd += serial_base.TX_16(control_id)
        cmd += serial_base.TX_8(0x02)
        cmd += serial_base.TX_8((precision &0x0F) | 0x80 )
        # 将float值转换为字节，并处理大小端 ,这里我们使用大端格式 '>f'，如果需要小端格式，可以使用 '<f'
        float_bytes = ustruct.pack('>f',floatvalue) 
        # 使用大端格式 
        cmd += float_bytes 
        cmd += serial_base.END_CMD()
        # print(cmd)
        self.uart_hmi.write(cmd)
    # 发送字节类型
    def SetTextInt32(self,screen_id, control_id,value,sign,fill_zero):
        ''' 
            brief  设置文本为整数值
            param  screen_id 画面ID
            param  control_id 控件ID
            param  value 文本数值
            param  sign 0-无符号,1-有符号
            param  fill_zero 位数，不足时左侧补零
        '''
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8(0xB1)
        cmd += serial_base.TX_8(0x07)
        cmd += serial_base.TX_16(screen_id)
        cmd += serial_base.TX_16(control_id)
        cmd += serial_base.TX_8(0x01 if sign else 0x00)
        cmd += serial_base.TX_8((fill_zero & 0x0F) | 0x80)
        cmd += serial_base.TX_32(value)
        cmd += serial_base.END_CMD()
        # print(cmd)
        self.uart_hmi.write(cmd)

    def SetTextValue(self,screen_id,control_id,str):
        '''
          brief     设置文本值
          param  screen_id 画面ID
          param  control_id 控件ID
          param  str 文本值(字符串)
        '''
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8(0xB1)
        cmd += serial_base.TX_8(0x10)
        cmd += serial_base.TX_16(screen_id)
        cmd += serial_base.TX_16(control_id)
        cmd += serial_base.SendStrings(str)
        cmd += serial_base.END_CMD()
        self.uart_hmi.write(cmd)

    def AnimationPlayFrame(self,screen_id,control_id,frame):
        '''
            brief  播放动画帧
            param  screen_id 画面ID
            param  control_id 控件ID
            param  frame 动画帧索引
        '''
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8(0xB1)
        cmd += serial_base.TX_8(0x23)
        cmd += serial_base.TX_16(screen_id)
        cmd += serial_base.TX_16(control_id)
        cmd += serial_base.TX_8(frame)
        cmd += serial_base.END_CMD()
        self.uart_hmi.write(cmd)
    def HistoryGraph_SetValue(self,screen_id, control_id,value,channel):
        """
        *  brief  设置历史曲线采样值
        *  param  screen_id 
        *  param  control_id 
        *  param  value 
        *  param  channel 
        """
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8(0xB1)
        cmd += serial_base.TX_8(0x60)
        cmd += serial_base.TX_16(screen_id)
        cmd += serial_base.TX_16(control_id)
        cmd += serial_base.TX_16(value)
        cmd += serial_base.TX_8(channel)
        cmd += serial_base.END_CMD()
        self.uart_hmi.write(cmd)
   
    def ReadRTC(self):
        '''
        brief: 读取屏幕的RTC时间,屏幕返回时间数据
        '''
        cmd = serial_base.BEGIN_CMD()
        cmd += serial_base.TX_8((0x2 & 0x0F) | 0x80)
        cmd += serial_base.END_CMD()
        self.uart_hmi.write(cmd)

    def register_calback(self,handler):
        '''
        brief: 注册回调函数
        '''
        if handler:
            self.hmi_hander = handler
        else:
            print("time data handler is None")

    def register_rtc_calback(self,handler):
        '''
        brief: 注册触摸屏RTC回调函数,读取触摸屏时间到系统RTC
        '''
        if handler:
            self.RTCcalback = handler
        else:
            print('RTC RtChandler is None')
        
        # 获取当前采集间隔
    def get_interval(self):
        return int(self.interval_logger)

    def get_collect_flag(self):
        return self.collect_flag

    def set_collect_flag(self,flag:bool):
        if flag:
            self.collect_flag = True
        else:
            self.collect_flag = False

   # UI更新
    def UpdataUI(self):
        try:
            if self.hmi_hander:
                self.recieve_data = self.hmi_hander()
                if self.recieve_data :
                    data = self.recieve_data.get('rm_10', [])
                    self.SetTextFloat(0,1,data[0],0)  # CO2
                    self.HistoryGraph_SetValue(1,1,urandom.uniform(500, 5000),1)
                    self.SetTextFloat(0,4,data[1],4)   # O2
                    self.HistoryGraph_SetValue(1,2,urandom.uniform(0, 25),1)
                    self.SetTextFloat(0,6,data[2],4)   # H2O 
                    self.HistoryGraph_SetValue(1,5,urandom.uniform(0, 15),1)
                    self.SetTextFloat(0,8,data[3],1)   # 温度
                    self.SetTextFloat(0,10,data[4],1)  # SFlow
                    self.SetTextFloat(0,12,data[5],1)  # CFlow
                    self.HistoryGraph_SetValue(1,6,urandom.uniform(1000, 3000),1)
                    self.SetTextFloat(0,37,data[6],1)  # Atm
                    self.SetTextFloat(0,38,data[7],1)  # CH8
                    self.SetTextFloat(0,16,data[8],1)  # CH9
                    self.SetTextFloat(0,18,data[9],1)  # CH10

                else:
                    print("no data")
                    return 
            else:
                return  
        except Exception as e:
            print("updata ui error:",e)
            return

    def timer0_cb(self,arg):
        if self.phase == 'blank':
            if self.current_channel and self.current_channel != 0:
                self._turn_off_channel(self.current_channel)
            self.current_channel = 0
            self._turn_on_channel(self.current_channel)
            self.phase = 'nonzero'
            self.sequence_index = 0
            
            self.AnimationPlayFrame(0, 52, 0)  # 非0动画常关闭
            self.AnimationPlayFrame(0, 51, 1) 
            self.timer0.stop()
            self.timer0.period(int(self.interval_0)*1000)
            self.timer0.start()
        elif self.phase == 'nonzero':
            if not self.sequence_channels:
                if self.current_channel and self.current_channel != 0:
                    self._turn_off_channel(self.current_channel)
                self.current_channel = 0
                self._turn_on_channel(self.current_channel)
                self.phase = 'blank'
                self.sequence_index = 0
                self.timer0.stop()
                self.timer0.period(int(self.interval_0)*1000)
                self.timer0.start()
                return
            ch = self.sequence_channels[self.sequence_index]
            prev_ch = self.current_channel
            if prev_ch and prev_ch != 0:
                self._turn_off_channel(prev_ch)
            self.current_channel = ch
            self._turn_on_channel(self.current_channel)
            self.sequence_index += 1
            if self.sequence_index > len(self.sequence_channels)-1:
                self.phase = 'blank'
                self.sequence_index = 0
                self.timer0.stop()
                self.timer0.period(int(self.interval_1_9)*1000)
                self.timer0.start()
            else:
                self._turn_off_channel(0)
                self.AnimationPlayFrame(0, 52, 1)  # 非0动画常打开
                self.AnimationPlayFrame(0, 51, 0) 
                self.timer0.stop()
                self.timer0.period(int(self.interval_1_9)*1000)
                self.timer0.start()
    
    def _turn_on_channel(self, ch):
        # print("turn on channel:",ch)
        channel_dry.channel_on("channel_%d" % ch)
        anim = self._channel_to_anim.get(ch)
        if anim is not None:
            self.AnimationPlayFrame(0, anim, 1)
       
           
    def _turn_off_channel(self, ch):
        # print("turn off channel:",ch)
        channel_dry.channel_off("channel_%d" % ch)
        anim = self._channel_to_anim.get(ch)
        if anim is not None:
            self.AnimationPlayFrame(0, anim, 0)
         
    


    
